<?php
/**
 * @author Eugenio Fage
 * http://eugeniofage.wordpress.com/
 * http://code.google.com/p/monky/
 * 
 */
class User extends AppModel {

	var $actsAs=array('Ban','Acl' => array('requester'));

	var $validate = array(
		'username' => array(
			'notempty' => array(
				'rule' => array('between', 5, 20),
			),'unique' =>array(
				'rule' => array('isUnique'),
			)
		),'password' => array(
			'lenght' => array(
				'rule' => array('confirmationAtLeast', 6),
        	)
       	),'email' => array(
			'email' => array(
				'rule' => array('email',true),
				'allowEmpty' => false,
        	),'unique' =>array(
				'rule' => array('isUnique'),
        	)
        )
	);
	
	// The Associations below have been created with all possible keys, those that are not needed can be removed

    var $hasMany = array(
		'Comment','Link','Vote'
	);

	var $hasAndBelongsToMany = array(
		'LinkVoted' => array(
			'className' => 'Link',
			'type'=>'LEFT',
			'joinTable' => 'votes',
			'foreignKey' => 'user_id',
			'associationForeignKey' => 'link_id',
			'with'=>'Votes',
			'fields'=>array(),
			'unique' => true,
			'conditions' => array('Votes.type'=>'links')
		)
	);
	
	var $belongsTo = array('Group');
	
	
	function bindNode($user) {
    	return array('Group' => array('id' => $user['User']['group_id']));
	}
	
	
	
	/**
	 * 
	 * if a check fails throws a addlinkexception with the error message ...
	 */
	function checkCanPostLinks($user_id){
		$this->id=$user_id;
		$checks=array(
			'basicCheck'=>array(),
			'normal'=>array('checkMinKarmaForAddLink','checkIpNotBanned','checkLinkAddByClons','checkLinksSentByUser','checkLinksSentByUserInLast3Minutes','checkUserHaveEnoughVotes','checkRubbishAndSpamSending'),
			'colaborator'=>array('checkUserHaveEnoughVotes','checkRubbishAndSpamSending'),
			'moderator'=>array('checkUserHaveEnoughVotes','checkRubbishAndSpamSending'),
			'admin'=>array(),
			'god'=>array()
		);
		
		try{
			foreach (am($checks['basicCheck'],$checks[$this->Group->field('Group.name',array('id'=>$this->field('group_id')))]) as $doCheck){
				$this->$doCheck();
			}
		}catch (UserErrorException $exception){
			throw new AddLinkException($exception->getMessage());
		}
	}

	function checkMinKarmaForAddLink(){
		$min_karma_for_links=Configure::read('Monky.Links.min_karma_for_add');
		if ($min_karma_for_links > 0 && $this->field('karma') < $min_karma_for_links ) {
			throw new AddLinkException(sprintf(__('so sorry! you dont have the minimum karma for send links, your karma its %.2f and must be greater than %.2f',true),$this->field('karma'),$min_karma_for_links));
		}
	}
	
	function checkIpNotBanned(){
		if($this->notBannedIp(Visitor::ip())) return;
		
		throw new UserErrorException(__('your IP is banned',true));
	}
		
	function checkLinkAddByClons(){
		// Don't allow to send a link by a clone 
		$hours = intval(Configure::read('Monky.Links.user_clon_interval'));
		$clones = ClassRegistry::init('Clon','model')->getClones($this->id,$hours+1);
		if ($hours > 0 && !empty($clones)) {
			$c = (int)$this->Link->find('count',array('conditions'=>array('Link.status'=>'published','Link.status_modified >'=>'date_sub(now(), interval '.$hours.' hour)', 'Link.user_id'=>$clones),'recursive'=>-1));
			if ($c > 0) {
				throw new AddLinkException(__('you have already send links with a clon user in the last hours',true));
			}
		}
	}
	
	function checkLinksSentByUser(){
		// Check the number of links sent by a user
		if($limit_user_24_hours=Configure::read('Monky.Links.limit_user_24_hours')>0){
			$links_not_published_in_24_hours = (int) $this->Link->find('count',array('conditions'=>array('NOT'=>array('Link.status'=>'published'),'Link.created > date_sub(now(), interval 24 hour)','Link.user_id'=>$this->id),'recursive'=>-1));

			if ($links_not_published_in_24_hours > $limit_user_24_hours) {
				throw new AddLinkException(__('debes esperar, tienes demasiados envíos en cola de las últimas 24 horas',true));
			}
		}
	}
	
	function checkLinksSentByUserInLast3Minutes(){
		
		if($this->field('karma') > Configure::read('Monky.Links.limit_3_minutes_karma')){
			$enqueued_limit = Configure::read('Monky.Links.limit_3_minutes') * 1.5;
		}else{
	 		$enqueued_limit = Configure::read('Monky.Links.limit_3_minutes');
		}
		
		$enqueued_last_minutes = (int) $this->Link->find('count',array('conditions'=>array('Link.status'=>'queued','Link.status_modified > date_sub(now(), interval 3 minute)','Link.user_id'=>$this->id),'recursive'=>-1));
		
		if ($enqueued_last_minutes > $enqueued_limit) {
			throw new AddLinkException(__('se han enviado demasiadas historias en los últimos 3 minutos',true));
		}
	}

		
	function checkUserHaveEnoughVotes(){
		// check that a new user also votes, not only sends links

		$needed=$this->getNeededVotesToAddLink();
		
		if($needed>0){
			throw new AddLinkException(
				sprintf(__('you need at least %s votes to be able to add links',true),$needed).
				sprintf(__('take your time to vote, if you vote quickly your karma will be decrease. <a href="%s">click here to start voting</a>',true),Router::url(array('controller'=>'links','action'=>'shakeit')))
			);
		}
		
	}

	
	
	function checkRubbishAndSpamSending(){
		// avoid spams, an extra security check
		// it counts the numbers of links in the last hours
		if ($this->isNewUSer()) {
			$user_links_limit = Configure::read('Monky.Links.new_user_limit');
			$user_links_interval = intval(Configure::read('Monky.Links.new_user_interval') / 3600);
		} else {
			$user_links_limit =Configure::read('Monky.Links.user_limit'); 
			$user_links_interval = intval(Configure::read('Monky.Links.user_interval') / 3600);
		}
		
		$same_user = $this->Link->find('count',array('conditions'=>array('Link.created > date_sub(now(), interval '.$user_links_interval.' minute)','Link.user_id'=>$this->id),'recursive'=>-1));
		$same_ip = $this->Link->find('count',array('conditions'=>array('Link.created > date_sub(now(), interval '.$user_links_interval.' minute)','Link.ip'=>Visitor::ip()),'recursive'=>-1));
		
		if ($same_user >  $user_links_limit  || $same_ip >  $user_links_limit  ) {
			throw new AddLinkException(__('debes esperar, ya se enviaron varias con el mismo usuario o dirección IP',true));
		}
	
		// avoid users sending continuous "rubbish" or "propaganda", specially new users
		// it takes in account the number of positive votes in the last six hours
		if ($same_user > 1 && $this->field('karma') < Configure::read('Monky.Karma.propaganda')) {
			$positives_received = $this->Link->field('sum(votes)',array('Link.status_modified > date_sub(now(), interval '.$user_links_interval.' minute)', 'Link.user_id'=>$this->id));
			//db->get_var("select sum(link_votes) from links where link_date > date_sub(now(), interval $user_links_interval hour) and link_author = $current_user->user_id");
			$negatives_received = $this->Link->field('sum(negatives)',array('Link.status_modified > date_sub(now(), interval '.$user_links_interval.' minute)', 'Link.user_id'=>$this->id));
			
			if ($negatives_received > 10 && $negatives_received > $positives_received * 1.5) {
				throw new AddLinkException(__('debes esperar, has tenido demasiados votos negativos en tus últimos envíos',true));
			}
		}
	}
	
	
	private function _getCacheAffinity($iduser){
		if($cache = Cache::read($iduser.'-affinity', 'fetchedurl')){
			if(count($cache)>0){
				Cache::set(array('duration' => '+15 days'));
			}else{
				Cache::set(array('duration' => '+2 days'));
			}
			return Cache::read($iduser.'-affinity');
		}
		return false;
	}
	
	private function _saveAffinity($iduser,$affinity) {
		if(count($affinity)>0){
			Cache::set(array('duration' => '+15 days'));
		}else{
			Cache::set(array('duration' => '+2 days'));
		}
		return Cache::write($iduser.'-affinity',$affinity);
	}
	
	function calculateAffinity($iduser,$minkarma=200){
		
		$affinity=array();
		// Check vote-to-links affinity
		$affinity=$this->Link->getAffinity($iduser,$affinity,$minkarma);
		
		// Check vote-to-comments affinity
		$affinity=$this->Comment->getAffinity($iduser,$affinity);

		if (count($affinity) == 0) {
			$affinity = false;
		}
			
		return $affinity;
	}
	
	function getAffinity($iduser, $from = false) {
		if(!$affinity = $this->_getCacheAffinity($iduser)){
			$affinity=$this->calculateAffinity($iduser);
			$this->_saveAffinity($iduser, $affinity);
		}
				
		if ($from && isset($affinity[$from])){
			$affinity=$affinity[$from];
		}
		
		return $affinity;
	}
	
	function isNewUSer(){
		$user_created=getUnixtime($this->field('created'));

		if ((time() - $user_created < Configure::read('Monky.User.new_user_time') ) || (!$this->getSentLinks()==0)) {
			return true;
		}
		
		return false;
	}
	
	// Karma average:  It's used for each link to check the balance of users' votes
	function getKarmaAvg($hours=72){
		$qry=$this->Link->find('all',array(
			'fields'=>'avg(votes_avg) all_votes_avg',
			'conditions'=>array('Link.status' => 'published','Link.status_modified > date_sub(now(), interval '.$hours.' hour)'),
			'recursive'=>-1			
		));
				
		if(empty($qry)) return 0;
		
		return (float)$qry[0][0]['all_votes_avg'];
	}
	 
	
	function getSentLinks($days=null){
		$conditions=array('Link.user_id'=>$this->id);
		if($days) $conditions[]='Link.created > date_sub(now(), interval '.$days.' day)';
		
		return $this->Link->find('count',array('conditions'=>$conditions,'recursive'=>-1));
	}
	
	function getRanking($user_id=null){
		if(!$user_id)$user_id=$this->id;
		return $this->find('count',array(
			'conditions'=>array('User.karma >'=>$this->field('User.karma',array('User.id'=>$user_id))),
			'recursive'=>-1
		))+ 1;
	}

	function checkCanPostComment($user_id,$link_id){
		if(!$user_id) return;
		
		// if the user its the owner of the link dont check if has enough karma
		if(!$this->Link->hasAny(array('id'=>$link_id,'user_id'=>$user_id))){
			$this->checkIfHasMinKarmaForComment($user_id);
		}
		
		$last=$this->Comment->find('first',array(
			'fields'=>'Comment.created',
			'conditions'=>array('Comment.user_id'=>$user_id,'Comment.link_id'=>$link_id),
			'order'=>'Comment.id DESC',
			'recursive'=>-1
		));
		if( isset($last['Comment']['created']) &&  Configure::read('Monky.User.reCommentTime') > 0){
			$timePass=time()-getUnixtime($last['Comment']['created']);
			if($timePass<Configure::read('Monky.User.reCommentTime')){
				$error=__('You must wait %d minutes to add another comment in this link',true);
				$error=sprintf($error,round((Configure::read('Monky.User.reCommentTime')-$timePass)/60));
				throw new CommentException($error);								
			}
		}
		
	}
	
	private function checkIfHasMinKarmaForComment($user_id){
		$minKarma=Configure::read('Monky.User.minKarmaForComment');
		$userKarma=$this->field('User.karma',array('id'=>$user_id));
		if($minKarma>0 && $userKarma<$minKarma){
			$error=__('Your karma is %.2f and you need at least %.2f for comment',true);
			$error=sprintf($error,$userKarma,$minKarma);
			throw new CommentException($error);
		}
	}
	
	
	function updateLastLogin($user_id){
		$this->id=$user_id;
		$this->save(array(
			'User'=>array(
				'ip'=>Visitor::ip_int(),
				'lastlogin'=>getTimestamp()
			)
		),false,array('ip','lastlogin'));
	}

	function getUserSignupsFromIp($ip,$hoursInterval=24){
		return $this->find('count',array(
			'conditions'=>array(
				'ip'=>$ip,
				"User.created > date_sub(now(), interval $hoursInterval hour)"
			),'recursive'=>-1
		));
	}
	
	function getIdLoginFromSameIp($loggedId,$hoursInterval=24){
		$userClon=$this->find('first',array(
			'fields'=>array('id'),
			'recursive'=>-1,
			'conditions'=>array(
				'User.lastlogin > date_sub(now(), interval '.$hoursInterval.' hour)',
				'User.ip'=> Visitor::ip_int(),
				'NOT'=>array('User.id'=>$loggedId)
			),
			'order'=>'User.lastlogin DESC'
		));
		
		if(isset($userClon['User']['id'])&&$userClon['User']['id']){
			return $userClon['User']['id'];
		}
		
		return false;
	}
	
	function doClonCheck($loggedId){
		if($clonId=$this->getIdLoginFromSameIp($loggedId)){
			$clon = ClassRegistry::init('Clon','model');
			$data=array('Clon'=>array('from'=>$clonId,'to'=>$loggedId,'ip'=>Visitor::ip_int()));
			
			if(!$clon->hasAny($data['Clon'])){
				$clon->save($data);
			}else {
				$clon->updateAll(array('Clon.created'=>'CURRENT_TIMESTAMP'),$data['Clon'],-1);
			}
		}		
	}
	
	

	
	
	
	
	function getNeededVotesToAddLink(){
		$min_votes = Configure::read('Monky.User.min_votes');
		if(!$min_votes) return 0;
				
		//sorry... few links, cant do the vote check
		$total_queued_links_in_last_3_days=$this->Link->find('count',array('recursive'=>-1,'conditions'=>array('Link.status'=>'queued','NOT'=>array('OR'=>array('Link.user_id'=>$this->id)),'Link.status_modified > date_sub(now(), interval 72 hour)')));
		
		if($total_queued_links_in_last_3_days<($min_votes*3)) return 0;
				
		if ($this->isNewUSer() && $this->field('karma') < Configure::read('Monky.User.new_user_karma')) {
			$user_links = 1 + $this->Link->find('count',array('conditions'=>array('Link.user_id'=>$this->id, 'Link.status_modified > date_sub(now(), interval 24 hour)', 'recursive'=>-1)));
			
			if ($user_links > 1) {
				$total_links = $this->Link->find('count',array('conditions'=>array('Link.status_modified > date_sub(now(), interval 24 hour)','Link.status'=> 'queued'),'recursive'=>-1));
				$min_votes = min(4, intval($total_links/20)) * $user_links;
			}
			
			$user_votes = $this->Vote->find('count',array('conditions'=>array('Vote.type'=>'links','Vote.user_id'=>$this->id,'Vote.created > date_sub(now(), interval 72 hour)'),'recursive'=>-1));
		}else{
			$user_votes = $this->getTotalVotesToLinksFromOthers($this->id);
			$min_votes = Configure::read('Monky.User.min_votes');
		}
				
		return $min_votes - $user_votes;
		
	}
	
	
	function getTotalVotesToLinksFromOthers($user_id,$hoursInterval=72){
		return $this->Vote->find('count',array(
			'conditions'=>array(
				'Vote.type'=>'links',
				'Vote.user_id'=>$user_id,
				'NOT'=>array('Vote.ip'=>Visitor::ip_int()),
				'Vote.created > date_sub(now(), interval '.$hoursInterval.' hour)',
			),
			'contain'=>array('Link'=>array(
            	'conditions'=> array(
            		'Link.id=Vote.link_id',
					'NOT'=>array('Link.user_id=Vote.user_id')
				)
			))			
		));		
	}
	
	
	/**
	 * 
	 * Just used by the validation trougth form registration
	 */
	function confirmationAtLeast($notUsed,$len){
		return strlen($this->data['User']['passwordconfirmation']) >=$len;
	}
}
